package com.life.ai.server.common.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.life.ai.server.common.excel.*;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * Excel相关处理
 * ExcelUtil
 *
 * @author kylewka
 * @since 2023/8/31
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ExcelUtil {

    /**
     * 同步导入(适用于小数据量)
     *
     * @param is    输入流
     * @param clazz 对象类型
     * @return 转换后集合
     */
    public static <T> List<T> read(InputStream is, Class<T> clazz) {
        return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
    }

    /**
     * 使用校验监听器
     *
     * @param is           输入流
     * @param clazz        对象类型
     * @param isValidate   是否 Validator 检验 默认为是
     * @param breakOnError 是否 异常时中断处理
     * @return 转换后集合
     */
    public static <T> ExcelResult<T> read(InputStream is, Class<T> clazz, boolean isValidate, boolean breakOnError) {
        DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate, breakOnError);
        EasyExcel.read(is, clazz, listener).sheet().doRead();
        return listener.getExcelResult();
    }

    /**
     * 导入Excel并返回导入成功的数量
     *
     * @param is    输入流
     * @param clazz 对象类型
     * @return 转换后集合
     */
    public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelConsumer<T> consumer) {
        CustomExcelListener<T> listener = new CustomExcelListener<>(consumer, false, false, false);
        EasyExcel.read(is, clazz, listener).sheet().doRead();
        return listener.getExcelResult();
    }

    /**
     * 导入Excel并返回导入成功的数量
     *
     * @param is           输入流
     * @param clazz        对象类型
     * @param isValidate   是否 Validator 检验 默认为是
     * @param breakOnError 是否 异常时中断处理
     * @return 转换后集合
     */
    public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelConsumer<T> consumer, boolean isValidate, boolean breakOnError) {
        CustomExcelListener<T> listener = new CustomExcelListener<>(consumer, isValidate, breakOnError, false);
        EasyExcel.read(is, clazz, listener).sheet().doRead();
        return listener.getExcelResult();
    }

    /**
     * 导入Excel并返回导入成功的数据
     *
     * @param is           输入流
     * @param clazz        对象类型
     * @param isValidate   是否 Validator 检验 默认为是
     * @param breakOnError 是否 异常时中断处理
     * @return 转换后集合
     */
    public static <T> ExcelResult<T> importExcelAndReturn(InputStream is, Class<T> clazz, ExcelConsumer<T> consumer, boolean isValidate, boolean breakOnError) {
        CustomExcelListener<T> listener = new CustomExcelListener<>(consumer, isValidate, breakOnError, true);
        EasyExcel.read(is, clazz, listener).sheet().doRead();
        return listener.getExcelResult();
    }

    /**
     * 导出excel
     *
     * @param list      导出数据集合
     * @param sheetName 工作表的名称
     * @param clazz     实体类
     * @param response  响应体
     */
    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
        try {
            excelResponse(sheetName, response);
            ServletOutputStream os = response.getOutputStream();
            ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
                    .autoCloseStream(true)
                    // 自动适配
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    // 大数值自动转换 防止失真
                    .registerConverter(new ExcelBigNumberConvert())
                    .sheet(sheetName);
            builder.doWrite(list);
        } catch (IOException e) {
            throw new RuntimeException("导出excel文件异常");
        }
    }

    /**
     * 导出excel
     *
     * @param list      导出数据集合
     * @param sheetName 工作表的名称
     * @param clazz     实体类
     * @param response  响应体
     */
    public static <T> void exportCsv(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
        try {
            csvResponse(sheetName, response);
            ServletOutputStream os = response.getOutputStream();

            EasyExcel.write(os, clazz).excelType(ExcelTypeEnum.CSV)
                    .charset(Charset.forName("GBK"))
                    .autoCloseStream(true)
                    .sheet(sheetName)
                    .doWrite(list);
        } catch (IOException e) {
            throw new RuntimeException("导出csv文件异常");
        }
    }

    /**
     * 单表多数据模板导出 模板格式为 {.属性}
     *
     * @param filename     文件名
     * @param templatePath 模板路径 resource 目录下的路径包括模板文件名
     *                     例如: excel/temp.xlsx
     *                     重点: 模板文件必须放置到启动类对应的 resource 目录下
     * @param data         模板需要的数据
     * @param response     响应体
     */
    public static void exportTemplate(List<Object> data, String filename, String templatePath, HttpServletResponse response) {
        try {
            excelResponse(filename, response);
            ServletOutputStream os = response.getOutputStream();
            exportTemplate(data, templatePath, os);
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常");
        }
    }


    /**
     * 单表多数据模板导出 模板格式为 {.属性}
     *
     * @param templatePath 模板路径 resource 目录下的路径包括模板文件名
     *                     例如: excel/temp.xlsx
     *                     重点: 模板文件必须放置到启动类对应的 resource 目录下
     * @param data         模板需要的数据
     * @param os           输出流
     */
    public static void exportTemplate(List<Object> data, String templatePath, OutputStream os) {
        ClassPathResource templateResource = new ClassPathResource(templatePath);
        ExcelWriter excelWriter = EasyExcel.write(os)
                .withTemplate(templateResource.getStream())
                .autoCloseStream(false)
                // 大数值自动转换 防止失真
                .registerConverter(new ExcelBigNumberConvert())
                .build();
        WriteSheet writeSheet = EasyExcel.writerSheet().build();
        if (CollUtil.isEmpty(data)) {
            throw new IllegalArgumentException("数据为空");
        }
        // 单表多数据导出 模板格式为 {.属性}
        for (Object d : data) {
            excelWriter.fill(d, writeSheet);
        }
        excelWriter.finish();
    }

    /**
     * 重置响应体
     */
    public static void excelResponse(String sheetName, HttpServletResponse response) throws UnsupportedEncodingException {
        String filename = sheetName + ExcelTypeEnum.XLSX.getValue();
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-disposition", "attachment; filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.toString()));
    }

    /**
     * 重置响应体
     */
    public static void csvResponse(String sheetName, HttpServletResponse response) throws UnsupportedEncodingException {
        String filename = sheetName + ExcelTypeEnum.CSV.getValue();
        response.setContentType("application/octet-stream");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-disposition", "attachment; filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.toString()));
    }
}
